package randomservice;

import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;

import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

public class TestTaskImpl implements ITestTask {

    static int START_QUOTA = 10;
    private static int RANDOM_LIMIT = 1000;

    private static int SECOND_LEVEL_CACHE_SIZE = 200;
    private static int USER_NUMBER = 1000000;

    private ConcurrentHashMap<Integer, AtomicLong> usersQuotas = new ConcurrentHashMap<Integer, AtomicLong>(USER_NUMBER);

    private ConcurrentMap<Integer, AtomicLong> secondCache = new ConcurrentLinkedHashMap.Builder<Integer, AtomicLong>()
            .maximumWeightedCapacity(SECOND_LEVEL_CACHE_SIZE)
            .build();

    private volatile UserQuota firstLevelCache;
    private volatile Set<Integer> amendedIds = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());

    private Random random = new Random();

    volatile boolean destroy = false;
    volatile boolean clearAll = false;

    private TestTaskPersistence testTaskPersistence;

    public TestTaskImpl() {
        testTaskPersistence = new TestTaskPersistence();
        testTaskPersistence.setTestTask(this, false);
    }

    public TestTaskImpl(TestTaskPersistence testTaskPersistence) {
        this.testTaskPersistence = testTaskPersistence;
        testTaskPersistence.setTestTask(this, true);
    }

    @Override
    public int getRandom(final int userId) throws QuotaExceededException {
        checkState();
        amendedIds.add(userId);
        AtomicLong quota = getUserQuota(userId, START_QUOTA);
        while (true) {
            long value = quota.get();
            if (value > 0 && quota.compareAndSet(value, value - 1)) {
                return random.nextInt(RANDOM_LIMIT);
            }
            if (value <= 0) {
                throw new randomservice.QuotaExceededException();
            }
        }
    }

    private void checkState() {
        if (destroy) {
            throw new IllegalStateException("Method destroy() have been executed");
        }
    }

    /**
     * Get quota for user. Initialize user in case such user wasn't initialized.
     *
     * @param userId
     * @return user's quota
     */
    AtomicLong getUserQuota(int userId, long initialQuota) {

        UserQuota localZeroCache = this.firstLevelCache;
        if (localZeroCache != null && localZeroCache.user == userId) {
            return localZeroCache.quota;
        }

        AtomicLong quota = secondCache.get(userId);
        if (quota != null) {
            firstLevelCache = new UserQuota(userId, quota);
            return quota;
        }

        quota = usersQuotas.get(userId);
        if (quota == null) {
            usersQuotas.putIfAbsent(userId, new AtomicLong(initialQuota));
            quota = usersQuotas.get(userId);
        }

        firstLevelCache = new UserQuota(userId, quota);
        secondCache.put(userId, quota);

        return quota;
    }

    Set<Integer> getChangedUsersAndClear() {
        Set<Integer> result = amendedIds;
        amendedIds = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
        return result;
    }

    @Override
    public void addQuota(final int userId, final long quota) {
        checkState();
        amendedIds.add(userId);
        AtomicLong oldQuota = getUserQuota(userId, START_QUOTA);
        addQuota(quota, oldQuota);
    }

    void addQuota(long quota, AtomicLong oldQuota) {
        while (true) {
            long value = oldQuota.get();
            if (oldQuota.compareAndSet(value, value + quota)) {
                return;
            }
        }
    }

    @Override
    public long getQuota(final int userId) {
        checkState();
        amendedIds.add(userId);
        return getUserQuota(userId, START_QUOTA).get();
    }

    @Override
    public void destroy() {
        checkState();
        System.out.println("Wait for the end of final synchronization");
        destroy = true;
        testTaskPersistence.destroy();
    }

    @Override
    public void clearAll() {
        checkState();
        clearAll = true;
        testTaskPersistence.clear();
        clearState();
        clearAll = false;

    }

    ConcurrentHashMap<Integer, AtomicLong> getUserQuotas() {
        return usersQuotas;
    }

    private static class UserQuota {

        final int user;
        final AtomicLong quota;

        public UserQuota(int user, AtomicLong quota) {
            this.user = user;
            this.quota = quota;
        }
    }

    void clearState() {
        usersQuotas.clear();
        firstLevelCache = null;
        secondCache.clear();
        amendedIds.clear();
    }

}
